/* 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.harmony.javax.naming;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OptionalDataException;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.apache.harmony.jndi.internal.nls.Messages;

/**
 * A <code>CompositeName</code> represents a name in a naming service which
 * spans multiple namespaces. For example the name "www.eclipse.org/index.html"
 * spans the DNS and file system namespaces.
 * <p>
 * A <code>CompositeName</code> is a series of string elements. A composite name
 * has a sequence of zero or more elements delimited by the '/' char. Each
 * element can be accessed using its position. The first element is at position
 * 0.
 * </p>
 * <p>
 * A <code>CompositeName</code> may be empty. An empty composite name has no
 * elements. Elements may also be empty.
 * </p>
 * <p>
 * <code>CompositeName</code>s are read from left to right unlike
 * <code>CompoundName</code>s which may have their direction of ordering
 * specified by properties.
 * </p>
 * <p>
 * Special characters are as follows:
 * </p>
 * <ul>
 * <li>The separator is /</li>
 * <li>The escape character is \</li>
 * <li>Quotes can be used - both single quotes and double quotes are allowed.
 * This allows you to quote strings which contain chars such as / which are part
 * of a <code>CompositeName</code> element to avoid them being read as a
 * separator.</li>
 * </ul>
 * <p>
 * See the examples for further clarification.
 * </p>
 * <p>
 * Some Examples:<br />
 * ==============
 * </p>
 * <p>
 * The composite name "www.eclipse.org/index.html" has 2 elements.
 * "www.eclipse.org" is a name from the DNS namespace. "index.html" is a name
 * from the file system namespace.
 * </p>
 * <p>
 * Another example of a composite name is: "www.eclipse.org/org/index.html".
 * This name has 3 elements "www.eclipse.org", "org" and "index.html".
 * www.eclipse.org is a name from the DNS namespace. The last 2 elements are
 * each from the file system namespace.
 * </p>
 * <p>
 * Some more examples to clarify empty names and elements:
 * </p>
 * <p>
 * An empty CompositeName is the name "" and has no elements.
 * </p>
 * <p>
 * A CompositeName with just one empty element is the name "/".
 * </p>
 * <p>
 * The name "/org/" has 3 elements. The first and last are empty.
 * </p>
 * <p>
 * The name "/a" has 2 elements. The first element is empty and the second
 * element is "a".
 * </p>
 * <p>
 * The name "a//a" has 3 elements. The middle element is empty and the first &
 * third elements are both "a".
 * </p>
 * <p>
 * The name "a/'b/a" is invalid as there is no closing quote for the '
 * character.
 * </p>
 * <p>
 * The name "a/'a/b/b" is invalid as there is no closing quote for the '
 * character.
 * </p>
 * <p>
 * The name "a/\"b/a" is interpreted as a/"b/a and is invalid as there is no
 * closing quote for the embedded escaped " character.
 * </p>
 * <p>
 * The name "a/'b/c'/a" has 3 elements. The middle element is b/c.
 * <p>
 * The name "a/a'a/b'/b" has 4 elements: Element 0 is "a". Element 1 is "a'a".
 * Element 2 is "b'". Element 3 is "b".
 * </p>
 * <p>
 * Interestingly the name "a/a'a/b/b" is valid and has 4 elements. This is
 * because the single quote char ' is not a leading quote and is embedded in an
 * element so is treated as a character. Element 0 is "a". Element 1 is "a'a".
 * Element 2 is "b". Element 3 is "b".
 * </p>
 * <p>
 * The name "\"abcd" gives an <code>InvalidNameException</code> as there is no
 * closing quote.
 * </p>
 * <p>
 * The name "'\"abcd'" gives one element of value "abcd.
 * </p>
 * <p>
 * The name "\\abcd" gives one element of value \abcd.
 * </p>
 * <p>
 * "" is empty. It has no elements. "/" has one empty element. "//" has 2 empty
 * elements. "/a/" has 3 elements the middle one is set to a. "///" has 3 empty
 * elements. "//a/" has 4 elements, the last but one is set to a.
 * </p>
 */
public class CompositeName implements Name {

	private static final long serialVersionUID = 1667768148915813118L;

	// status used by parse()
	private static final int OUT_OF_QUOTE = 0;

	private static final int IN_SINGLE_QUOTE = 1;

	private static final int IN_DOUBLE_QUOTE = 2;

	private static final int QUOTE_ENDED = 3;

	/*
	 * Format name elements to its string representation.
	 */
	private static String formatName(Vector<String> elems) {
		final StringBuilder sb = new StringBuilder();
		final int elemSize = elems.size();
		if (isAllEmptyElements(elems)) {
			// special case: all empty elements
			for (int index = 0; index < elemSize; index++) {
				sb.append('/');
			}
			return sb.toString();
		}

		// general case
		String elem = null;
		for (int index = 0; index < elemSize; index++) {
			elem = elems.get(index);
			if (index > 0) {
				sb.append('/');
			}
			// Add quotation while elem contains separator char
			if (elem.indexOf('/') != -1) {
				sb.append('\"');
				sb.append(elem);
				sb.append('\"');
			} else {
				sb.append(elem);
			}
		}
		return sb.toString();
	}

	private static boolean isAllEmptyElements(Vector<String> elems) {
		final int elemSize = elems.size();
		for (int index = 0; index < elemSize; index++) {
			if (elems.get(index).length() > 0) {
				return false;
			}
		}
		return true;
	}

	private static boolean isAllSlash(String name) {
		final char[] nameChars = name.toCharArray();
		for (final char nameChar : nameChars) {
			if (nameChar != '/') {
				return false;
			}
		}
		return true;
	}

	/**
	 * Parse string name elements. Delimiter is "/". Escape is "\" and both
	 * single quote and double quote are supported.
	 */
	private static Vector<String> parseName(String name)
			throws InvalidNameException {
		final Vector<String> result = new Vector<String>();
		// special case: all '/', means same number of empty elements
		if (isAllSlash(name)) {
			final int length = name.length();
			for (int index = 0; index < length; index++) {
				result.add(""); //$NON-NLS-1$
			}
			return result;
		}

		// general simple case, without escape and quote
		if (name.indexOf('"') < 0 && name.indexOf('\'') < 0
				&& name.indexOf('\\') < 0) {
			int i = 0, j = 0;
			while ((j = name.indexOf('/', i)) >= 0) {
				result.add(name.substring(i, j));
				i = j + 1;
			}
			result.add(name.substring(i));
			return result;
		}

		// general complicated case, consider escape and quote
		char curC, nextC;
		final char chars[] = name.toCharArray();
		final StringBuilder buf = new StringBuilder();
		int status = OUT_OF_QUOTE;
		for (int index = 0; index < chars.length; index++) {
			curC = chars[index];

			// check end quote violation
			if (status == QUOTE_ENDED) {
				if (curC == '/') {
					result.add(buf.toString());
					buf.setLength(0);
					status = OUT_OF_QUOTE;
					continue;
				}
				// jndi.0C=End quote is not at the end of element
				throw new InvalidNameException(Messages.getString("jndi.0C")); //$NON-NLS-1$
			}

			if (curC == '\\') {
				// escape char
				try {
					nextC = chars[++index];
					if (nextC == '\\' || nextC == '\'' || nextC == '"'
							|| nextC == '/') {
						buf.append(nextC);
					} else {
						buf.append(curC);
						buf.append(nextC);
					}
				} catch (final ArrayIndexOutOfBoundsException e) {
					// jndi.0D=Escape cannot be at the end of element
					throw new InvalidNameException(
							Messages.getString("jndi.0D")); //$NON-NLS-1$
				}
				continue;
			}
			if (curC != '/' && curC != '"' && curC != '\'') {
				// normal char
				buf.append(curC);
				continue;
			}

			// special char
			if (status == OUT_OF_QUOTE && curC == '/') {
				result.add(buf.toString());
				buf.setLength(0);
			} else if (status == OUT_OF_QUOTE && curC == '\''
					&& buf.length() == 0) {
				status = IN_SINGLE_QUOTE;
			} else if (status == OUT_OF_QUOTE && curC == '"'
					&& buf.length() == 0) {
				status = IN_DOUBLE_QUOTE;
			} else if (status == IN_SINGLE_QUOTE && curC == '\'') {
				status = QUOTE_ENDED;
			} else if (status == IN_DOUBLE_QUOTE && curC == '"') {
				status = QUOTE_ENDED;
			} else {
				buf.append(curC);
			}
		}
		result.add(buf.toString());

		// check end status
		if (status != OUT_OF_QUOTE && status != QUOTE_ENDED) {
			// jndi.0E=Wrong quote usage.
			throw new InvalidNameException(Messages.getString("jndi.0E")); //$NON-NLS-1$
		}
		return result;
	}

	/* a list holding elements */
	private transient Vector<String> elems;

	/**
	 * Default constructor, creates an empty name with zero elements.
	 */
	public CompositeName() {
		super();
		elems = new Vector<String>();
	}

	/**
	 * Construct a composite name with given elements.
	 * 
	 * @param elements
	 *            an enumeration of name elements
	 */
	protected CompositeName(Enumeration<String> elements) {
		super();
		elems = new Vector<String>();
		while (elements.hasMoreElements()) {
			elems.add(elements.nextElement());
		}
	}

	/**
	 * Private copy constructor.
	 * 
	 * @param elements
	 *            a list of name elements
	 */
	private CompositeName(List<String> elements) {
		super();
		elems = new Vector<String>(elements);
	}

	/**
	 * This constructor takes the supplied name and breaks it down into its
	 * elements.
	 * 
	 * @param name
	 *            a string containing the full composite name
	 * @throws InvalidNameException
	 *             if the supplied name is invalid
	 */
	public CompositeName(String name) throws InvalidNameException {
		super();
		elems = parseName(name);
	}

	@Override
	public Name add(int index, String element) throws InvalidNameException {
		if (index < 0 || index > elems.size()) {
			throw new ArrayIndexOutOfBoundsException();
		}
		elems.add(index, element);
		return this;
	}

	@Override
	public Name add(String element) throws InvalidNameException {
		elems.add(element);
		return this;
	}

	@Override
	public Name addAll(int index, Name name) throws InvalidNameException {
		if (null == name) {
			throw new NullPointerException();
		}
		if (!(name instanceof CompositeName)) {
			// jndi.0F=Must be a CompositeName
			throw new InvalidNameException(Messages.getString("jndi.0F")); //$NON-NLS-1$
		}

		if (index < 0 || index > elems.size()) {
			throw new ArrayIndexOutOfBoundsException();
		}
		final Enumeration<String> enumeration = name.getAll();
		while (enumeration.hasMoreElements()) {
			elems.add(index++, enumeration.nextElement());
		}
		return this;
	}

	@Override
	public Name addAll(Name name) throws InvalidNameException {
		if (null == name) {
			throw new NullPointerException();
		}
		if (!(name instanceof CompositeName)) {
			// jndi.0F=Must be a CompositeName
			throw new InvalidNameException(Messages.getString("jndi.0F")); //$NON-NLS-1$
		}

		final Enumeration<String> enumeration = name.getAll();
		while (enumeration.hasMoreElements()) {
			elems.add(enumeration.nextElement());
		}
		return this;
	}

	/**
	 * Create a copy of this composite name, a complete (deep) copy of the
	 * object.
	 * 
	 * @return a complete (deep) copy of the object.
	 */
	@Override
	public Object clone() {
		return new CompositeName(elems);
	}

	/**
	 * Compare this <code>Name</code> with the one supplied as a parameter. The
	 * elements of the names are compared in the same way as strings are
	 * compared to determine whether this <code>CompositeName</code> is less
	 * than, greater than or equal to the supplied object <code>o</code>.
	 * 
	 * @param o
	 *            the object to compare, cannot be null
	 * @return a negative number means this is less than the supplied object; a
	 *         positive number means this is greater than the supplied object;
	 *         zero means this CompositeName equals the object as specified in
	 *         the description for the equals method of
	 *         <code>CompositeName</code>.
	 * @throws ClassCastException
	 *             when <code>o</code> is not a <code>CompositeName</code>.
	 */
	@Override
	public int compareTo(Object o) {
		if (o == this) {
			return 0;
		}
		if (!(o instanceof CompositeName)) {
			throw new ClassCastException();
		}

		final Iterator<String> thisIter = elems.iterator();
		final Iterator<String> thatIter = ((CompositeName) o).elems.iterator();
		int compareResult;
		while (thisIter.hasNext() && thatIter.hasNext()) {
			compareResult = thisIter.next().compareTo(thatIter.next());
			if (0 != compareResult) {
				return compareResult;
			}
		}
		if (thisIter.hasNext()) {
			return 1;
		}
		if (thatIter.hasNext()) {
			return -1;
		}
		return 0;
	}

	@Override
	public boolean endsWith(Name name) {
		if (!(name instanceof CompositeName)) {
			return false;
		}
		// check size
		if (name.size() > elems.size()) {
			return false;
		}

		// compare 1 by 1
		final Enumeration<String> enumeration = name.getAll();
		for (int index = elems.size() - name.size(); enumeration
				.hasMoreElements(); index++) {
			if (!elems.get(index).equals(enumeration.nextElement())) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Check if this <code>CompositeName</code> is equal to the supplied object.
	 * 
	 * @param o
	 *            the <code>CompositeName</code> to compare - can be null but
	 *            then returns false.
	 * @return true if they have the same number of elements all of which are
	 *         equal. false if they are not equal.
	 */
	@Override
	public boolean equals(Object o) {
		// check type
		if (!(o instanceof CompositeName)) {
			return false;
		}
		final CompositeName that = (CompositeName) o;
		if (size() != that.size()) {
			return false;
		}

		final Iterator<String> thisIter = elems.iterator();
		final Iterator<String> thatIter = that.elems.iterator();
		while (thisIter.hasNext() && thatIter.hasNext()) {
			if (!thisIter.next().equals(thatIter.next())) {
				return false;
			}
		}
		return true;
	}

	@Override
	public String get(int index) {
		return elems.get(index);
	}

	@Override
	public Enumeration<String> getAll() {
		return elems.elements();
	}

	@Override
	public Name getPrefix(int index) {
		if (index < 0 || index > elems.size()) {
			throw new ArrayIndexOutOfBoundsException();
		}
		return new CompositeName(elems.subList(0, index));
	}

	@Override
	public Name getSuffix(int index) {
		if (index < 0 || index > elems.size()) {
			throw new ArrayIndexOutOfBoundsException();
		}
		return new CompositeName(elems.subList(index, elems.size()));
	}

	/**
	 * Calculate the hashcode of this <code>CompositeName</code> by summing the
	 * hash codes of all of its elements.
	 * 
	 * @return the hashcode of this object.
	 */
	@Override
	public int hashCode() {
		int hashCode = 0;
		for (int index = 0; index < elems.size(); index++) {
			hashCode += elems.get(index).hashCode();
		}
		return hashCode;
	}

	@Override
	public boolean isEmpty() {
		return elems.isEmpty();
	}

	/**
	 * Recreate a CompositeName from the data in the supplied stream.
	 * 
	 * @param ois
	 * @throws IOException
	 *             if an error is encountered reading from the stream.
	 * @throws ClassNotFoundException.
	 */
	private void readObject(ObjectInputStream ois)
			throws OptionalDataException, ClassNotFoundException, IOException {
		ois.defaultReadObject();

		final int size = ois.readInt();
		elems = new Vector<String>();
		for (int i = 0; i < size; i++) {
			elems.add((String) ois.readObject());
		}
	}

	@Override
	public Object remove(int index) throws InvalidNameException {
		if (index < 0 || index >= elems.size()) {
			throw new ArrayIndexOutOfBoundsException();
		}
		return elems.remove(index);
	}

	@Override
	public int size() {
		return elems.size();
	}

	@Override
	public boolean startsWith(Name name) {
		if (!(name instanceof CompositeName)) {
			return false;
		}
		// check size
		if (name.size() > elems.size()) {
			return false;
		}
		// compare 1 by 1
		final Enumeration<String> enumeration = name.getAll();
		for (int index = 0; enumeration.hasMoreElements(); index++) {
			if (!elems.get(index).equals(enumeration.nextElement())) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Returns the string representation of this <code>CompositeName</code>.
	 * This is generated by concatenating the elements together with the '/'
	 * char added as the separator between each of them. It may be necessary to
	 * add quotes and escape chars to preserve the meaning. The resulting string
	 * should produce an equivalent <code>CompositeName</code> when used to
	 * create a new instance.
	 * 
	 * @return the string representation of this composite name.
	 */
	@Override
	public String toString() {
		return formatName(elems);
	}

	/**
	 * Writes a serialized representation of the CompositeName. It starts with
	 * an int which is the number of elements in the name, and is followed by a
	 * String for each element.
	 * 
	 * @param oos
	 * @throws IOException
	 *             if an error is encountered writing to the stream.
	 */
	private void writeObject(ObjectOutputStream oos) throws IOException {
		oos.defaultWriteObject();

		oos.writeInt(elems.size());
		for (final Object element : elems) {
			oos.writeObject(element);
		}
	}
}
